@file:Suppress("PropertyName", "unused", "MemberVisibilityCanBePrivate")

package dev.entao.core.slices

import dev.entao.base.DAY
import dev.entao.base.Encrypt
import dev.entao.base.MaxLength
import dev.entao.core.HttpAction
import dev.entao.core.HttpContext
import dev.entao.core.HttpSlice
import dev.entao.core.header
import dev.entao.json.YsonObject
import dev.entao.sql.*
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation


//检查是否登录
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LoginToken(val type: String)

val HttpContext.tokenInfo: TokenInfo get() = this.infoMap[TokenSlice.TOKEN_INFO] as? TokenInfo ?: emptyTokenInfo

private val emptyTokenInfo = TokenInfo()

object TokenSlice : HttpSlice {
    private const val header: String = """{"alg":"HS256","typ":"JWT"}"""
    internal const val TOKEN_INFO = "__TOKEN_INFO__"
    var pwd: String = "1234567890"
    var expireDays: Int = 30

    fun removeToken(id: Int, type: String) {
        TokenTable.delete(TokenTable::id EQ id, TokenTable::type EQ type)
    }

    fun removeToken(token: String) {
        TokenTable.delete(TokenTable::token EQ token)
    }


    fun makeToken(ident: Int, type: String, expireTime: Long = System.currentTimeMillis() + expireDays.DAY, platform: String = "app"): String {
        val m = TokenInfo.from(ident, type, expireTime, platform)
        val token = JWT.make(pwd, m.toString(), header)
        val ut = TokenTable()
        ut.id = ident
        ut.type = type
        ut.expireTime = expireTime
        ut.platform = platform
        ut.token = token
        ut.saveByKey()
        return token
    }


    override fun beforeRouter(context: HttpContext, cls: KClass<*>, action: HttpAction) {
        val accept = action.hasAnnotation<LoginToken>() || cls.hasAnnotation<LoginToken>()
        if (!accept) {
            return
        }
        val token = context.tokenText ?: return context.sendError(401)
        val j = JWT(pwd, token)
        if (!j.OK) return context.sendError(401)
        val m = TokenInfo(YsonObject(j.body))
        if (m.expired) return context.sendError(401)
        val an: LoginToken? = action.findAnnotation() ?: cls.findAnnotation()
        if (an?.type != m.type) return context.sendError(401)
        TokenTable.one(TokenTable::token EQ token) ?: return context.sendError(401)
        context.infoMap[TOKEN_INFO] = m
        context.accountID = m.id
        context.accountType = m.type
    }


}

val HttpContext.tokenText: String?
    get() {
        request.header("Authorization")?.substringAfter("Bearer ", "")?.trim()?.also {
            if (it.isNotEmpty()) return it
        }
        val tk = this["access_token"] ?: this["token"] ?: return null
        return tk.ifEmpty {
            null
        }
    }

class TokenTable : Model() {

    @PrimaryKey
    var id: Int by model

    @PrimaryKey
    var type: String by model

    @Index
    var expireTime: Long by model

    @Index
    var platform: String by model

    @Index
    @MaxLength(1024)
    var token: String by model

    companion object : ModelClass<TokenTable>()
}


class TokenInfo(private val yo: YsonObject = YsonObject()) {
    var id: Int by yo
        private set
    var type: String by yo
        private set
    var expireTime: Long by yo
        private set
    var platform: String by yo
        private set

    val expired: Boolean
        get() {
            if (expireTime != 0L) {
                return System.currentTimeMillis() >= expireTime
            }
            return false
        }

    override fun toString(): String {
        return yo.toString()
    }

    companion object {
        fun from(ident: Int, type: String, expireTime: Long, platform: String): TokenInfo {
            val a = TokenInfo()
            a.id = ident
            a.type = type
            a.expireTime = expireTime
            a.platform = platform
            return a
        }
    }

}


private class JWT(pwd: String, token: String) {
    var header: String = ""
    var body: String = ""
    var sign: String = ""
    var OK: Boolean = false

    init {
        val ls = token.split('.')
        if (ls.size == 3) {
            val h = ls[0]
            val d = ls[1]
            val g = ls[2]
            val m = Encrypt.hmacSha256("$h.$d", pwd)
            if (m == g) {
                header = Encrypt.B64.decode(h)
                body = Encrypt.B64.decode(d)
                sign = g
                OK = true
            }
        }
    }

    companion object {

        fun make(pwd: String, data: String, header: String = """{"alg":"HS256","typ":"JWT"}"""): String {
            val a = Encrypt.B64.encode(header)
            val b = Encrypt.B64.encode(data)
            val m = Encrypt.hmacSha256("$a.$b", pwd)
            return "$a.$b.$m"
        }
    }
}
